﻿using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LightCAD.MathLib
{

    /// <summary>
    /// 放样体
    /// </summary>
    public class Loft3d : Solid3d
    {
        private PlanarSurface3d profile;
        private List<Curve3d> path;
        private Vector3 profileCenter;
        private Plane workPlane;

        public PlanarSurface3d Profile { get => profile; set => SetProperty(value, path, profileCenter); }
        public List<Curve3d> Path { get => path; set => SetProperty(profile, value, profileCenter); }

        public Vector3 ProfileCenter { get => profileCenter; set => SetProperty(profile, path, value); }


        public Loft3d() { }

        public Loft3d(PlanarSurface3d profile, List<Curve3d> path, Vector3 profileCenter)
        {
            this.SetProperty(profile, path, profileCenter);
        }

        public Surface3d[] Surfaces;
        private TopoFaceModel topoFaceModel;
        public Loft3d SetProperty(PlanarSurface3d profile, List<Curve3d> path, Vector3 profileCenter)
        {
            if (topoFaceModel != null && (this.profile != profile ||
                                          this.path != path ||
                                          this.path.Count != path.Count) ||
                                          this.profileCenter != profileCenter)
            {
                topoFaceModel = null;
            }
            this.profile = profile;
            this.path = path;
            this.profileCenter = profileCenter;
            return this;
        }
        public override TopoFaceModel CreateTopoModel()
        {
            if (topoFaceModel != null)
                return topoFaceModel;

            var surfaces = new List<Surface3d>();
            var workPlane = GetWorkPlane();
            for (int i = 0; i < path.Count; i++)
            {
                var pathSegment = path[i];
                var pathPoints = pathSegment.GetPoints();
                var pathStart = pathPoints[0];
                var pathEnd = pathPoints[1];
                var pathDir = (pathEnd - pathStart).Normalize();
                var angle = -profile.Normal.AngleTo(pathDir);
                profile.Translate(pathStart);
                profile.RotateRoundAxis(pathStart, workPlane.Normal, angle);

                for (int j = 0; j < profile.OuterLoop.Count; j++)
                {
                    var loop = profile.OuterLoop[j];
                    var loopPoints = loop.GetPoints();
                    for (int t = 0; t < loopPoints.Count - 1; t++)
                    {
                        var loopStart = loop.Start;
                        var loopEnd = loop.End;
                        var pathOffset = pathEnd - pathStart;

                        var p1 = loopStart.Clone();
                        var p2 = loopEnd.Clone();
                        var p3 = p1 + pathOffset;
                        var p4 = p2 + pathOffset;
                        var planeFace = new PlanarSurface3d(new Plane().SetFromCoplanarPoints(p1, p3, p2), new List<Curve3d>()
                        {
                            new Line3d(p1, p2),
                            new Line3d(p2, p4),
                            new Line3d(p4, p3),
                            new Line3d(p3, p1)
                        });
                        surfaces.Add(planeFace);
                    }
                }
            }

            this.Surfaces = surfaces.ToArray();
            this.topoFaceModel = new TopoFaceModel() { Surfaces = this.Surfaces.ToListEx() };

            return this.topoFaceModel;
        }

        public Plane GetWorkPlane()
        {
            if (workPlane != null)
                return workPlane;

            Vector3 p1 = null;
            Vector3 p2 = null;
            Vector3 p3 = null;
            if (this.path.Count == 1)
            {
                var points = path[0].GetPoints();
                if (path[0].GetPoints().Count == 2)
                {
                    return new Plane().SetFromNormalAndCoplanarPoint(new Vector3(0, 0, 1), path[0].Start);
                }
                else
                {
                    p1 = points[0];
                    p2 = points[1];
                    p3 = points[2];
                }
            }
            else
            {
                p1 = path[0].Start;
                p2 = path[0].End;
                p3 = path[1].End;
            }

            workPlane = new Plane().SetFromCoplanarPoints(p1, p3, p2);
            return workPlane;
        }

        private PlanarSurface3d GeneratesPlanarSurface(Vector3 origin, Vector3 axis, double angle)
        {
            var plane = profile.Plane.Clone();
            var mat = new Matrix4().MakeRotationAxis(axis, angle);
            plane.ApplyMatrix4(mat);

            var outloop = new List<Curve3d>();
            foreach (var loop in profile.OuterLoop)
            {
                outloop.Add(loop.Clone().RotateRoundAxis(origin, axis, angle));
            }

            return new PlanarSurface3d(plane, outloop);
        }

        public override Solid3d CreateMesh()
        {
            //base.Mesh();
            //base.MeshOneMaterial(this.Surfaces);

            var allPoints = new ListEx<Vector3>();
            var indices = new ListEx<int>();
            var pathPoints = new List<Vector3>();
            var workPlane = GetWorkPlane();
            foreach (var curve in path)
            {
                var points = curve.GetPoints();
                if (pathPoints.Count != 0 && Utils.Vec3EQ(pathPoints.Last(), points.First()))
                {
                    points = points.Skip(1).ToList();
                }
                pathPoints.AddRange(points);
            }
            bool closed = Utils.Vec3EQ(pathPoints.Last(), pathPoints.First());//首尾是否相连
            var startFacePoints = new ListEx<Vector3>(); //开始面经过路径偏移后的轮廓点
            var endFacePoints = new ListEx<Vector3>(); //结束面经过路径偏移后的轮廓点
            for (int i = 0; i < pathPoints.Count - 1; i++)
            {
                if (i > 2)
                {
                    //continue;
                }
                int lasti = i - 1; //上一段curve的开始点
                int endi = i + 1; //当前curve的结束点
                int nexi = i + 2; //下一段curve的结束点
                Vector3 lastStart = null;
                Vector3 nextEnd = null;
                if (lasti > -1)
                {
                    lastStart = pathPoints[lasti];
                }
                else 
                {
                    if (closed)
                    {
                        lastStart = pathPoints[pathPoints.Count - 2];
                    }
                }

                if (nexi < pathPoints.Count)
                {
                    nextEnd = pathPoints[nexi];
                }
                else //首尾是否相连
                {
                    if (closed)
                    {
                        nextEnd = pathPoints[1];
                    }
                }
                Vector3 start = pathPoints[i];
                Vector3 end = pathPoints[endi];
                for (int j = 0; j < profile.OuterLoop.Count; j++)
                {
                    var loop = profile.OuterLoop[j];
                    var loopPoints = loop.GetPoints();
                    var curDir = (end - start).Normalize();
                    Quaternion curQuat = new Quaternion().SetFromUnitVectors(curDir, profile.Normal).Invert();

                    var curFacePoints = new List<Vector3>();
                    for (int t = 0; t < loopPoints.Count - 1; t++)
                    {
                        int nxt = t + 1;
                        var loopStart = loopPoints[t].Clone();
                        var loopEnd = loopPoints[nxt].Clone();
                        loopStart.ApplyQuaternion(curQuat);
                        loopEnd.ApplyQuaternion(curQuat);
                        loopStart += start;
                        loopEnd += start;

                        var pathOffset = end - start;
                        var p1 = loopStart.Clone();
                        var p2 = loopEnd.Clone();
                        var p3 = p1 + pathOffset;
                        var p4 = p2 + pathOffset;

                        if (lastStart != null) //计算当前curve与上一段curve的交点
                        {
                            var lastDir = (start - lastStart).Normalize();
                            Quaternion lastQuat = new Quaternion().SetFromUnitVectors(lastDir, profile.Normal).Invert();

                            var lastLoopStart = loopPoints[t].Clone();
                            var lastLoopEnd = loopPoints[nxt].Clone();
                            lastLoopStart.ApplyQuaternion(lastQuat);
                            lastLoopEnd.ApplyQuaternion(lastQuat);
                            lastLoopStart += lastStart;
                            lastLoopEnd += lastStart;

                            var lastPathOffset = start - lastStart;
                            var lastP1 = lastLoopStart.Clone();
                            var lastP2 = lastLoopEnd.Clone();
                            var lastP3 = lastP1 + lastPathOffset;
                            var lastP4 = lastP2 + lastPathOffset;

                            var intersection = Utils.LineIntersectLine2D(lastP1.ToVector2(), (lastP3 - lastP1).Normalize().ToVector2(), p3.ToVector2(), (p1 - p3).Normalize().ToVector2());
                            if (intersection != null)
                            {
                                p1 = intersection.ToVector3(p1.Z);
                            }
                            intersection = Utils.LineIntersectLine2D(lastP2.ToVector2(), (lastP4 - lastP2).Normalize().ToVector2(), p4.ToVector2(), (p2 - p4).Normalize().ToVector2());
                            if (intersection != null)
                            {
                                p2 = intersection.ToVector3(p2.Z);
                            }
                        }
                        else //第一段且路径不闭合
                        {
                            startFacePoints.Add(p1);
                        }

                        if (nextEnd != null) //计算当前curve与下一段curve的交点
                        {
                            var nextDir = (nextEnd - end).Normalize();
                            //var nextAngle = GetAngle(nextDir.ToVector2(), profile.Normal.ToVector2());
                            Quaternion nextQuat = new Quaternion().SetFromUnitVectors(nextDir, profile.Normal).Invert();
                            var nextLoopStart = loopPoints[t].Clone();
                            var nextLoopEnd = loopPoints[nxt].Clone();
                            nextLoopStart.ApplyQuaternion(nextQuat);
                            nextLoopEnd.ApplyQuaternion(nextQuat);
                            nextLoopStart += end;
                            nextLoopEnd += end;

                            var nextPathOffset = nextEnd - end;
                            var nextP1 = nextLoopStart.Clone();
                            var nextP2 = nextLoopEnd.Clone();
                            var nextP3 = nextP1 + nextPathOffset;
                            var nextP4 = nextP2 + nextPathOffset;

                            var intersection = Utils.LineIntersectLine2D(p1.ToVector2(), (p3 - p1).Normalize().ToVector2(), nextP3.ToVector2(), (nextP1 - nextP3).Normalize().ToVector2());
                            if (intersection != null)
                            {
                                p3 = intersection.ToVector3(p1.Z);
                            }
                            intersection = Utils.LineIntersectLine2D(p2.ToVector2(), (p4 - p2).Normalize().ToVector2(), nextP4.ToVector2(), (nextP2 - nextP4).Normalize().ToVector2());
                            if (intersection != null)
                            {
                                p4 = intersection.ToVector3(p2.Z);
                            }
                        }
                        else //最后一段缺路径不闭合
                        {
                            endFacePoints.Add(p3);
                        }

                        var count = allPoints.Count;
                        indices.Push(count, count + 1, count + 2);
                        allPoints.Push(p1, p4, p2);

                        count = allPoints.Count;
                        indices.Push(count, count + 1, count + 2);
                        allPoints.Push(p1, p3, p4);
                    }


                }
            }

            if (startFacePoints.Count > 0)
            {
                var startFaceCurve = new List<Curve3d>();
                for (int i = 0; i < startFacePoints.Count; i++)
                {
                    var nxi = (i + 1) % startFacePoints.Count;
                    var sp = startFacePoints[i];
                    var ep = startFacePoints[nxi];
                    startFaceCurve.Add(new Line3d(sp, ep));
                }
                var planeFace = new PlanarSurface3d(new Plane().SetFromCoplanarPoints(startFacePoints[0], startFacePoints[1], startFacePoints[2]), startFaceCurve);
                var geoData = planeFace.Trianglate();
                int idxArrCount = allPoints.Count();
                indices.AddRange(geoData.Item2.Select(idx => idx += idxArrCount));
                for (int i = 0; i < geoData.Item1.Length; i+=3)
                {
                    double x = geoData.Item1[i];
                    double y = geoData.Item1[i + 1];
                    double z = geoData.Item1[i + 2];
                    allPoints.Add(new Vector3(x, y, z));
                }
            }

            if (endFacePoints.Count > 0)
            {
                var endFaceCurve = new List<Curve3d>();

                endFacePoints.Reverse();
                for (int i = 0; i < endFacePoints.Count; i++)
                {
                    var nxi = (i + 1) % endFacePoints.Count;
                    var sp = endFacePoints[i];
                    var ep = endFacePoints[nxi];
                    endFaceCurve.Add(new Line3d(sp, ep));
                }
                var planeFace = new PlanarSurface3d(new Plane().SetFromCoplanarPoints(endFacePoints[0], endFacePoints[1], endFacePoints[2]), endFaceCurve);
                var geoData = planeFace.Trianglate();

                int idxArrCount = allPoints.Count();
                indices.AddRange(geoData.Item2.Select(idx => idx += idxArrCount));
                for (int i = 0; i < geoData.Item1.Length; i += 3)
                {
                    double x = geoData.Item1[i];
                    double y = geoData.Item1[i + 1];
                    double z = geoData.Item1[i + 2];
                    allPoints.Add(new Vector3(x, y, z));
                }
            }

            var posArr = Utils.GenerateVertexArr(allPoints);
            this.Geometry = new GeometryData();
            this.Geometry.Verteics = posArr;
            this.Geometry.Indics = indices.ToArray();
            this.Geometry.Groups = new GeometryGroup[1]
            {
                new GeometryGroup{ Name = "Geometry", Start = 0, Count = this.Geometry.Indics.Length, MaterialIndex = 0 },
            };
            #region 绘制路径
            var edgePoints = new ListEx<Vector3>();
            var edgeIndices = new ListEx<int>();
            foreach (var loop in path)
            {
                var points = loop.GetPoints();
                for (int i = 0; i < points.Count - 1; i++)
                {
                    int nxi = i + 1;

                    var count = edgePoints.Count;
                    edgeIndices.Push(count, count + 1);
                    edgePoints.Push(points[i], points[nxi]);
                }
            }
            //for (int i = 0, edgeCount = this.Edge.Verteics.Length / 3; i < indices.Length; i++)
            //{
            //    indices[i] += edgeCount;
            //}

            var pathVerteics = Utils.GenerateVertexArr(edgePoints);

            //this.Edge.Verteics = this.Edge.Verteics.Concat(pathVerteics).ToArray();
            //this.Edge.Indics = this.Edge.Indics.Concat(indices).ToArray();

            this.Edge = new GeometryData();
            this.Edge.Verteics = pathVerteics;
            this.Edge.Indics = edgeIndices.ToArray();
            #endregion
            return this;
        }

        private double GetAngle(Vector2 dir1, Vector2 dir2)
        {
            var angle1 = dir1.Angle();
            var angle2 = dir2.Angle();
            return angle1 - angle2;
        }
    }
}
